package com.hzya.frame;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hzya.frame.dto.ResultDataDto;
import com.hzya.frame.dto.ResultDto;
import okhttp3.*;
import org.junit.Test;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @Author：liuyang
 * @Package：com.hzya.frame
 * @Project：nifi-hzyadev-bundle
 * @name：GetJackyunOpenDataProcessorTest
 * @Date：2025/8/27 16:04
 * @Filename：GetJackyunOpenDataProcessorTest
 */
public class GetJackyunOpenDataProcessorTest2 {

    // 定义统一的时间格式化器
    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 新增的方法：按小时调度查询任务
     * 这个方法模拟在2025年8月27日，从 00:00:00 开始，每小时查询一次数据，
     * 直到最后一个时间段覆盖到 18:16:00。
     */
    /**
     * 新增的方法：按小时调度查询任务
     * 这个方法模拟在2025年8月27日，从 00:00:00 开始，每小时查询一次数据，
     * 直到最后一个时间段覆盖到 18:16:00，并最后输出总计。
     */
    /**
     * 修正后的方法：按小时调度查询任务
     * 通过将每个时间段的结束时间减去1秒，来避免边界数据的重复计算，
     * 确保查询区间为 [start, end)，实现无缝且不重叠的查询。
     */
    @Test
    public void queryDataHourly() {
        // 设置任务的起始和最终截止时间
        LocalDateTime taskStartTime = LocalDateTime.of(2025, 8, 27, 0, 0, 0);
        LocalDateTime taskFinalEndTime = LocalDateTime.of(2025, 8, 27, 18, 16, 0);

        LocalDateTime currentQueryStartTime = taskStartTime;

        long grandTotal = 0L;

        System.out.println("开始执行按小时查询任务（已修正边界问题）...");

        while (currentQueryStartTime.isBefore(taskFinalEndTime)) {
            // 定义下一个小时的起点，作为当前区间的“开”边界
            LocalDateTime nextHourStartTime = currentQueryStartTime.plusHours(1);

            // 确定本次查询的实际结束时间
            LocalDateTime actualQueryEndTime;

            // 如果下一个小时的起点已经超过了最终截止时间，或者就是最终截止时间
            // 那么本次查询的结束时间就应该是最终截止时间，这是最后一个区间
            if (nextHourStartTime.isAfter(taskFinalEndTime) || nextHourStartTime.isEqual(taskFinalEndTime)) {
                actualQueryEndTime = taskFinalEndTime;
            } else {
                // 否则，本次查询的结束时间是下一个小时的起点减去1秒
                // 例如，对于 [00:00:00, 01:00:00) 的区间，实际查询的是 [00:00:00, 00:59:59]
                actualQueryEndTime = nextHourStartTime.minusSeconds(1);
            }

            // 将时间对象格式化为字符串
            String startTimeStr = DATETIME_FORMATTER.format(currentQueryStartTime);
            String endTimeStr = DATETIME_FORMATTER.format(actualQueryEndTime);

            try {
                long totalCountInHour = getTotalCountForTimeRange(startTimeStr, endTimeStr);
                System.out.println("时间范围: " + startTimeStr + " - " + endTimeStr + " 查询数量: " + totalCountInHour);
                grandTotal += totalCountInHour;
            } catch (Exception e) {
                System.err.println("查询时间范围 " + startTimeStr + " - " + endTimeStr + " 时发生错误: " + e.getMessage());
                e.printStackTrace();
            }

            // 下一次循环的开始时间，直接就是下一个小时的整点
            currentQueryStartTime = nextHourStartTime;
        }

        System.out.println("所有时间段查询完毕。");
        System.out.println("查询合计总数量: " + grandTotal);
    }

    /**
     * 辅助方法：获取指定时间范围内的总数据量（包含分页逻辑）
     * @param startTimeStr 查询开始时间 (格式: "yyyy-MM-dd HH:mm:ss")
     * @param endTimeStr 查询结束时间 (格式: "yyyy-MM-dd HH:mm:ss")
     * @return 该时间范围内的总记录数
     * @throws IOException API请求异常
     * @throws NoSuchAlgorithmException MD5加密异常
     */
    private long getTotalCountForTimeRange(String startTimeStr, String endTimeStr) throws IOException, NoSuchAlgorithmException {
        long total = 0L;
        int pageIndex = 0;
        // 循环调用API，直到返回的数据为空，实现分页查询
        while (true) {
            // 调用API，传入分页索引和时间范围
            ResultDataDto resultDataDto = callJackyunApi(pageIndex++, startTimeStr, endTimeStr);
            if (resultDataDto != null && resultDataDto.getResult() != null) {
                ResultDto result = resultDataDto.getResult();
                // 如果返回的数据列表不为空且有数据
                if (result.getData() != null && !result.getData().isEmpty()) {
                    total += result.getData().size();
                } else {
                    // 如果没有数据返回，说明已经查询完所有分页，跳出循环
                    break;
                }
            } else {
                // 如果返回结果为空，也跳出循环
                break;
            }
        }
        return total;
    }

    /**
     * 修改后的API调用方法
     * @param pageIndex 页码
     * @param auditTimeBegin 审核开始时间
     * @param auditTimeEnd 审核结束时间
     * @return API返回结果
     * @throws IOException API请求异常
     * @throws NoSuchAlgorithmException MD5加密异常
     */
    private ResultDataDto callJackyunApi(Integer pageIndex, String auditTimeBegin, String auditTimeEnd) throws IOException, NoSuchAlgorithmException {
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .build();

        // 将 auditTimeBegin 和 auditTimeEnd 参数动态拼接到 bizData 中
        String bizData = "{\n" +
                "    \"tradeNo\": \"\",\n" +
                "    \"settleStatus\": \"\",\n" +
                "    \"gmtModifiedEnd\": \"\",\n" +
                "    \"auditTimeBegin\": \"" + auditTimeBegin + "\",\n" +
                "    \"pageSize\": \"200\",\n" +
                "    \"gmtCreatedBegin\": \"\",\n" +
                "    \"billDateBegin\": \"\",\n" +
                "    \"settleNo\": \"\",\n" +
                "    \"billDateEnd\": \"\",\n" +
                "    \"gmtCreatedEnd\": \"\",\n" +
                "    \"pageIndex\": \"" + pageIndex + "\",\n" +
                "    \"auditTimeEnd\": \"" + auditTimeEnd + "\",\n" +
                "    \"detailCols\": \"tradeSettleOrder.settleId, tradeSettleOrder.shopId, tradeSettleOrder.auditor, tradeSettleOrder.flagIds, tradeSettleOrder.payment, tradeSettleOrder.departId, tradeSettleOrder.settleNo, tradeSettleOrder.shopName, tradeSettleOrder.auditTime, tradeSettleOrder.auditorId, tradeSettleOrder.companyId, tradeSettleOrder.settleFee, tradeSettleOrder.settlerId, tradeSettleOrder.chargeType, tradeSettleOrder.departName, tradeSettleOrder.settleMemo, tradeSettleOrder.settleTime, tradeSettleOrder.sysFlagIds, tradeSettleOrder.accountName, tradeSettleOrder.companyName, tradeSettleOrder.settleStatus, tradeSettleOrder.chargeAccount, tradeSettleOrder.onlinePayTime, tradeSettleOrder.chargeCurrency, tradeSettleOrder.chargeCurrencyCode, tradeSettleOrder.chargeExchangeRate, tradeSettleOrder.settler, tradeSettleOrder.couponFee, tradeSettleOrder.differenceFee, tradeSettleOrder.settleTotalFee, tradeSettleOrder.settleDiscountFee, tradeSettleOrderDetailArr.subTradeId31177, tradeSettleOrderDetailArr.tradeFrom, tradeSettleOrderDetailArr.cost, tradeSettleOrderDetailArr.unit, tradeSettleOrderDetailArr.payNo, tradeSettleOrderDetailArr.cateId, tradeSettleOrderDetailArr.remark, tradeSettleOrderDetailArr.seller, tradeSettleOrderDetailArr.specId, tradeSettleOrderDetailArr.taxFee, tradeSettleOrderDetailArr.brandId, tradeSettleOrderDetailArr.goodsId, tradeSettleOrderDetailArr.goodsNo, tradeSettleOrderDetailArr.orderNo, tradeSettleOrderDetailArr.payTime, tradeSettleOrderDetailArr.payType, tradeSettleOrderDetailArr.taxRate, tradeSettleOrderDetailArr.cateName, tradeSettleOrderDetailArr.sellerId, tradeSettleOrderDetailArr.settleId, tradeSettleOrderDetailArr.specName, tradeSettleOrderDetailArr.auditTime, tradeSettleOrderDetailArr.brandName, tradeSettleOrderDetailArr.goodsName, tradeSettleOrderDetailArr.goodsTags, tradeSettleOrderDetailArr.sellCount, tradeSettleOrderDetailArr.sellPrice, tradeSettleOrderDetailArr.sellTotal, tradeSettleOrderDetailArr.tradeTime, tradeSettleOrderDetailArr.tradeType, tradeSettleOrderDetailArr.arriveTime, tradeSettleOrderDetailArr.chargeType, tradeSettleOrderDetailArr.goodsAlias, tradeSettleOrderDetailArr.logisticId, tradeSettleOrderDetailArr.mainPostid, tradeSettleOrderDetailArr.payAccount, tradeSettleOrderDetailArr.payDueDate, tradeSettleOrderDetailArr.stockoutNo, tradeSettleOrderDetailArr.accountName, tradeSettleOrderDetailArr.consignTime, tradeSettleOrderDetailArr.discountFee, tradeSettleOrderDetailArr.tradeStatus, tradeSettleOrderDetailArr.warehouseId, tradeSettleOrderDetailArr.lastShipTime, tradeSettleOrderDetailArr.logisticName, tradeSettleOrderDetailArr.logisticType, tradeSettleOrderDetailArr.chargeAccount, tradeSettleOrderDetailArr.sourceTradeNo, tradeSettleOrderDetailArr.warehouseName, tradeSettleOrderDetailArr.goodsAttribute, tradeSettleOrderDetailArr.shareFavourableAfterFee, tradeSettleOrderDetailArr.tradeNo, tradeSettleOrderDetailArr.innerSettleCost, tradeSettleOrderDetailArr.shareDiscountFee, tradeSettleOrderDetailArr.shareOtherPayment, tradeSettleOrderDetailArr.goodsReceiptAmount, tradeSettleOrderDetailArr.shareFairSellTotal, tradeSettleOrderDetailArr.shareFavourableFee, tradeSettleOrderDetailArr.otherShareFavourableFee, tradeSettleOrderDetailArr.customizeGoodsColumn1, tradeSettleOrderDetailArr.customizeGoodsColumn2, tradeSettleOrderDetailArr.customizeGoodsColumn3, tradeSettleOrderDetailArr.customizeGoodsColumn4, tradeSettleOrderDetailArr.customizeGoodsColumn5, tradeSettleOrderDetailArr.customizeGoodsColumn6, tradeSettleOrderDetailArr.customizeGoodsColumn7, tradeSettleOrderDetailArr.customizeGoodsColumn8, tradeSettleOrderDetailArr.customizeGoodsColumn9, tradeSettleOrderDetailArr.customizeGoodsColumn10, tradeSettleOrderCustomer.settleUnitId, tradeSettleOrderCustomer.qq, tradeSettleOrderCustomer.email, tradeSettleOrderCustomer.weiXin, tradeSettleOrderCustomer.wangWang, tradeSettleOrderCustomer.customerId, tradeSettleOrderCustomer.settleUnit, tradeSettleOrderCustomer.customerName, tradeSettleOrderCustomer.customerTags, tradeSettleOrderCustomer.customerType, tradeSettleOrderCustomer.customerGrade, tradeSettleOrderCustomer.customerAccount, tradeSettleOrderCustomer.customerTypeName, tradeSettleOrderCustomer.customerGradeName, tradeSettleOrderCustomer.endCustomerAccount, tradeSettleOrderColumnExt.customizeTradeColumn1, tradeSettleOrderColumnExt.customizeTradeColumn2, tradeSettleOrderColumnExt.customizeTradeColumn3, tradeSettleOrderColumnExt.customizeTradeColumn4, tradeSettleOrderColumnExt.customizeTradeColumn5, tradeSettleOrderColumnExt.customizeTradeColumn6, tradeSettleOrderColumnExt.customizeTradeColumn7, tradeSettleOrderColumnExt.customizeTradeColumn8, tradeSettleOrderColumnExt.customizeTradeColumn9, tradeSettleOrderColumnExt.customizeTradeColumn10\",\n" +
                "    \"gmtModifiedBegin\": \"\",\n" +
                "    \"hasQueryHistory\": \"\",\n" +
                "    \"shopId\": \"\",\n" +
                "    \"cols\": \"settleId,settleNo,tradeNo,sourceTradeNo,settleInvoiceStatus\"\n" +
                "}";

        SortedMap<String, String> sortedMap = new TreeMap<>();
        sortedMap.put("method", "oms.open.trade.settle.allinfo");
        sortedMap.put("appkey", "12346738");
        sortedMap.put("version", "v1.0");
        sortedMap.put("contenttype", "json");
        sortedMap.put("timestamp", DATETIME_FORMATTER.format(LocalDateTime.now()));
        sortedMap.put("bizcontent", bizData);

        StringBuilder sbSignData = new StringBuilder("94d715fc68214ce1ba48803b3bf19a9f");
        for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
            sbSignData.append(entry.getKey()).append(entry.getValue());
        }
        sbSignData.append("94d715fc68214ce1ba48803b3bf19a9f");

        sortedMap.put("sign", md5Encrypt(sbSignData.toString().toLowerCase()));

        FormBody.Builder formBodyBuilder = new FormBody.Builder();
        for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
            formBodyBuilder.add(entry.getKey(), entry.getValue());
        }
        RequestBody formBody = formBodyBuilder.build();

        Request request = new Request.Builder().url("https://open.jackyun.com/open/openapi/do").post(formBody).build();

        try (Response response = httpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("非预期的HTTP响应码: " + response.code() + ", 响应体: " + response.body().string());
            }
            return JSON.parseObject(response.body().string(), ResultDataDto.class);
        }
    }

    /**
     * MD5加密工具方法
     * @param text 待加密的字符串
     * @return 加密后的32位小写MD5字符串
     * @throws NoSuchAlgorithmException
     */
    private String md5Encrypt(String text) throws NoSuchAlgorithmException {
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        byte[] resultByte = text.getBytes(StandardCharsets.UTF_8);
        byte[] md5Bytes = md5.digest(resultByte);
        StringBuilder hexValue = new StringBuilder();
        for (byte md5Byte : md5Bytes) {
            int val = (md5Byte) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    /*
     * 保留您原有的 test1 方法，不做修改
     */
    @Test
    public void test1() {
        try {
            Long total = 0L;
            Integer pageIndex = 0;
            Set<String> ids = new HashSet<>();
            while (true) {
                // 为了让这个旧方法能运行，我们让它调用修改后的API方法，但传入固定的时间
                ResultDataDto resultDataDto = callJackyunApi(pageIndex++, "2025-08-27 00:00:00", "2025-08-27 18:16:00");
                if (resultDataDto != null && resultDataDto.getResult() != null) {
                    ResultDto result = resultDataDto.getResult();
                    if (result.getData() != null && result.getData().size() > 0) {
                        total += result.getData().size();
                        System.out.println("查询得到数量：" + result.getData().size() + " 总数量：" + total);
                        List<Object> data = result.getData();
                        for (int i = 0; i < data.size(); i++) {
                            JSONObject o = (JSONObject) data.get(i);
                            JSONObject tradeSettleOrder = (JSONObject) o.get("tradeSettleOrder");
                            String id1 = String.valueOf(tradeSettleOrder.get("id"));
                            // 关键修改：检查重复并报错
                            if (!ids.add(id1)) { // add() 返回 false 说明重复
                                throw new IllegalStateException("发现重复 ID: " + id1);
                            }
                        }
                    } else {
                        System.out.println("查询完毕");
                        break;
                    }
                }
            }
            System.out.println("总数量：" + total + " set集合：" + ids.size());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}